# -*- coding: utf-8 -*-

from typing import List
import pandas as pd

from com.maycur.utils import TimeUtils
from com.maycur.utils import CommonUtils
import math


class UrlMetaInfo:
    code: str
    url: str
    method: str
    comment: str
    modules: List[str]

    def __init__(self, code, url, method, comment):
        self.code = code
        self.url = url
        self.method = method
        self.comment = comment
        self.modules = []

    def add_module_permission(self, module_permission: str):
        if not pd.isnull(module_permission) and module_permission not in self.modules:
            self.modules.append(module_permission)


class UrlCodePermCodeMapping:
    url_code: str
    perm_code: str

    def __init__(self, url_code, perm_code):
        self.url_code = url_code
        self.perm_code = perm_code


class ControllerInfo:
    name: str
    # 统计@RequestMapping没有下沉到方法上的Controller
    base_request_mapping: str
    api_list: List[UrlMetaInfo]

    def __init__(self, name: str, base_request_mapping: str, api_list: List[UrlMetaInfo]):
        self.name = name
        self.base_request_mapping = base_request_mapping
        if self.base_request_mapping != '':
            for api_item in api_list:
                full_url = self.base_request_mapping + '/' + api_item.url
                api_item.url = full_url.replace('//', '/')
        self.api_list = api_list


class UrlMetaSqlGenerator:
    excel_file: str
    change_set_id_prefix: str
    change_set_author: str
    url_meta_info_list: List[UrlMetaInfo]
    latest_parse_url_meta_info: UrlMetaInfo
    sql_batch_size: int
    cur_change_set_idx: int
    cur_idx: int

    def __init__(self, change_set_id_prefix, change_set_id_start_index, change_set_author, the_excel_file):
        self.excel_file = the_excel_file
        self.change_set_id_prefix = change_set_id_prefix
        self.cur_change_set_idx = change_set_id_start_index
        self.change_set_author = change_set_author
        self.url_meta_info_list = []
        self.sql_batch_size = 100
        self.cur_idx = 0
        # 开始解析Excel
        self.parse_by_excel()

    def set_sql_batch_size(self, batch_size: int):
        self.sql_batch_size = batch_size

    def get_cur_change_set_idx(self):
        res = self.cur_change_set_idx
        self.cur_change_set_idx += 1
        return res

    def parse_by_excel(self):
        """
        默认实现：按Excel生成初始化SQL
        """
        if self.excel_file is None:
            return
        method_and_url_set = set()
        df = pd.read_excel(self.excel_file)
        for i in df.index.values:
            raw_url = df.iloc[i, 0]
            raw_method = df.iloc[i, 1]
            raw_module_permission = df.iloc[i, 2]
            raw_comment = df.iloc[i, 3]
            if pd.isnull(raw_url):
                self.latest_parse_url_meta_info.add_module_permission(raw_module_permission)
            else:
                self.check_url_path_param(raw_url)

                if type(raw_method) == str and type(raw_url) == str:
                    combine_item = raw_method + raw_url
                else:
                    raise RuntimeError(f'Line {i + 1}, url and method is invalid for this tool')
                if combine_item in method_and_url_set:
                    print(f'Line {i + 1}, duplicate method: {raw_method} and url: {raw_url}')
                method_and_url_set.add(combine_item)

                if pd.isnull(raw_comment):
                    raw_comment = ''
                url_meta_info = UrlMetaInfo(self.generate_code(), raw_url, raw_method, raw_comment)
                url_meta_info.add_module_permission(raw_module_permission)
                self.url_meta_info_list.append(url_meta_info)
                self.latest_parse_url_meta_info = url_meta_info

    def parse_by_controllers(self, controller_list: List[ControllerInfo]):
        if controller_list is None or len(controller_list) == 0:
            return
        for controller in controller_list:
            for api_item in controller.api_list:
                api_item.code = self.generate_code()
                self.url_meta_info_list.append(api_item)

    @staticmethod
    def check_url_path_param(check_url: str):
        bracket_l = '{'
        bracket_r = '}'
        bracket_pairs = 0
        for ch in check_url:
            if ch == bracket_l:
                bracket_pairs += 1
            elif ch == bracket_r:
                bracket_pairs -= 1
        if bracket_pairs != 0:
            raise RuntimeError(f'Invalid url found: {check_url}')

    def generate_code(self):
        """
        结果示例：URLINF202205100001
        """
        self.cur_idx += 1
        suffix = CommonUtils.l_padding(str(self.cur_idx), 4, '0')
        return f'URLINF{TimeUtils.current_date_str()}{suffix}'

    def generate_change_set_url_meta_info(self, url_meta_info_index: int):
        first_item = self.url_meta_info_list[url_meta_info_index]
        values_sql = ''
        url_list_len = len(self.url_meta_info_list)
        stop_index = url_meta_info_index + self.sql_batch_size
        for idx in range(url_meta_info_index, stop_index):
            if idx >= url_list_len:
                break
            item = self.url_meta_info_list[idx]
            code = item.code
            method = item.method
            url = item.url
            name = item.comment
            if len(name) >= 128:
                name = name[:120] + '...'
            description = item.comment
            if len(description) >= 512:
                description = description[:500] + '...'
            flag = 0
            if len(item.modules) == 0:
                flag = 1
            values_sql += f"\t\t\t\t('{code}', null, '{method}', '{url}', '{name}', '{description}', {flag}, 'SYSTEM', now())"
            if idx < stop_index - 1 and idx < url_list_len - 1:
                values_sql += ',\n'

        padded_id_idx = CommonUtils.l_padding(str(self.get_cur_change_set_idx()), 2, '0')
        return f'''
        <changeSet id="{self.change_set_id_prefix}-{padded_id_idx}" author="{self.change_set_author}">
            <preConditions onFail="MARK_RAN">
                <sqlCheck expectedResult="0">select count(1) from `url_meta_information` where `request_method` = '{first_item.method}' and `url` = '{first_item.url}'</sqlCheck>
            </preConditions>
            <sql>
                insert into `url_meta_information`
                (`code`, `parent_code`, `request_method`, `url`, `name`, `description`, `white_list_flag`, `created_by`,`created_at`)
                values\n{values_sql};
            </sql>
        </changeSet>
        '''

    def generate_change_set_url_perm_relation(self, url_perm_relation_idx: int, url_perm_mappings: List[UrlCodePermCodeMapping]) -> str:
        values_sql = ''
        url_perm_relation_len = len(url_perm_mappings)
        stop_index = url_perm_relation_idx + self.sql_batch_size
        for idx in range(url_perm_relation_idx, stop_index):
            if idx >= url_perm_relation_len:
                break
            item = url_perm_mappings[idx]

            values_sql += f"\t\t\t\t('{item.url_code}', '{item.perm_code}')"
            if idx < stop_index - 1 and idx < url_perm_relation_len - 1:
                values_sql += ',\n'

        padded_id_idx = CommonUtils.l_padding(str(self.get_cur_change_set_idx()), 2, '0')
        return f'''
        <changeSet id="{self.change_set_id_prefix}-{padded_id_idx}" author="{self.change_set_author}">
            <sql>
                insert into `url_perm_relation` (`url_code`, `perm_code`) 
                values\n{values_sql};
            </sql>
        </changeSet>
        '''

    def generate_url_meta_info_sql(self):
        for i in range(math.ceil(len(self.url_meta_info_list) / self.sql_batch_size)):
            change_set = self.generate_change_set_url_meta_info(self.sql_batch_size * i)
            print(change_set)

    def generate_url_perm_relation_sql(self):
        url_perm_mappings = []
        for u_item in self.url_meta_info_list:
            if len(u_item.modules) > 0:
                for perm_code in u_item.modules:
                    url_perm_mappings.append(UrlCodePermCodeMapping(u_item.code, perm_code))

        for i in range(math.ceil(len(url_perm_mappings) / self.sql_batch_size)):
            change_set = self.generate_change_set_url_perm_relation(self.sql_batch_size * i, url_perm_mappings)
            print(change_set)


if __name__ == '__main__':
    # Liquibase changeSet id前缀
    id_prefix = 'maycur-gateway-plugin-20220601-dml'
    # Liquibase changeSet id后缀下标
    id_start_index = 0
    # Liquibase changeSet author
    author = 'liangtj'
    # 待解析Excel文件
    excel_f = '/Users/tianjun315/Desktop/URL-Permission/All-Module-Permission.xlsx'

    sql_generator = UrlMetaSqlGenerator(id_prefix, id_start_index, author, excel_f)
    # 生成url_meta_information表insert语句
    sql_generator.generate_url_meta_info_sql()
    # 生成url_perm_relation表insert语句
    sql_generator.generate_url_perm_relation_sql()
